const fs = require('fs');
const install = function (Vue, options) {
  Vue.prototype.$call = (key, ...args) => context => context[key](...args);
  Vue.prototype.$collectInto = fn => (...args) => fn(args);
  Vue.prototype.$flip = fn => (...args) => fn(args.pop(), ...args);
  Vue.prototype.$pipeFunctions = (...fns) => fns.reduce((f, g) => (...args) => g(f(...args)));
  Vue.prototype.$promisify = func => (...args) =>
    new Promise((resolve, reject) =>
      func(...args, (err, result) => (err ? reject(err) : resolve(result)))
    );
  Vue.prototype.$spreadOver = fn => argsArr => fn(...argsArr);
  Vue.prototype.$chunk = (arr, size) =>
    Array.from({ length: Math.ceil(arr.length / size) }, (v, i) =>
      arr.slice(i * size, i * size + size)
    );
  Vue.prototype.$compact = arr => arr.filter(Boolean);
  Vue.prototype.$countBy = (arr, fn) =>
    arr.map(typeof fn === 'function' ? fn : val => val[fn]).reduce((acc, val, i) => {
      acc[val] = (acc[val] || 0) + 1;
      return acc;
    }, {});
  Vue.prototype.$countOccurrences = (arr, val) => arr.reduce((a, v) => (v === val ? a + 1 : a + 0), 0);
  Vue.prototype.$deepFlatten = arr => [].concat(...arr.map(v => (Array.isArray(v) ? deepFlatten(v) : v)));
  Vue.prototype.$difference = (a, b) => {
    const s = new Set(b);
    return a.filter(x => !s.has(x));
  };
  Vue.prototype.$differenceWith = (arr, val, comp) => arr.filter(a => val.findIndex(b => comp(a, b)) === -1);
  Vue.prototype.$distinctValuesOfArray = arr => [...new Set(arr)];
  Vue.prototype.$dropElements = (arr, func) => {
    while (arr.length > 0 && !func(arr[0])) arr = arr.slice(1);
    return arr;
  };
  Vue.prototype.$dropRight = (arr, n = 1) => arr.slice(0, -n);
  Vue.prototype.$everyNth = (arr, nth) => arr.filter((e, i) => i % nth === nth - 1);
  Vue.prototype.$filterNonUnique = arr => arr.filter(i => arr.indexOf(i) === arr.lastIndexOf(i));
  Vue.prototype.$findLast = (arr, fn) => arr.filter(fn).slice(-1);
  Vue.prototype.$flatten = (arr, depth = 1) =>
    depth != 1
      ? arr.reduce((a, v) => a.concat(Array.isArray(v) ? flatten(v, depth - 1) : v), [])
      : arr.reduce((a, v) => a.concat(v), []);
  Vue.prototype.$forEachRight = (arr, callback) =>
    arr
      .slice(0)
      .reverse()
      .forEach(callback);
  Vue.prototype.$groupBy = (arr, fn) =>
    arr.map(typeof fn === 'function' ? fn : val => val[fn]).reduce((acc, val, i) => {
      acc[val] = (acc[val] || []).concat(arr[i]);
      return acc;
    }, {});
  Vue.prototype.$head = arr => arr[0];
  Vue.prototype.$indexOfAll = (arr, val) => {
    const indices = [];
    arr.forEach((el, i) => el === val && indices.push(i));
    return indices;
  };
  Vue.prototype.$initial = arr => arr.slice(0, -1);
  Vue.prototype.$initialize2DArray = (w, h, val = null) =>
    Array.from({ length: h }).map(() => Array.from({ length: w }).fill(val));
  Vue.prototype.$initializeArrayWithRange = (end, start = 0, step = 1) =>
    Array.from({ length: Math.ceil((end + 1 - start) / step) }).map((v, i) => i * step + start);
  Vue.prototype.$initializeArrayWithValues = (n, val = 0) => Array(n).fill(val);
  Vue.prototype.$intersection = (a, b) => {
    const s = new Set(b);
    return a.filter(x => s.has(x));
  };
  Vue.prototype.$isSorted = arr => {
    const direction = arr[0] > arr[1] ? -1 : 1;
    for (let [i, val] of arr.entries())
      if (i === arr.length - 1) return direction;
      else if ((val - arr[i + 1]) * direction > 0) return 0;
  };
  Vue.prototype.$join = (arr, separator = ',', end = separator) =>
    arr.reduce(
      (acc, val, i) =>
        i == arr.length - 2
          ? acc + val + end
          : i == arr.length - 1 ? acc + val : acc + val + separator,
      ''
    );
  Vue.prototype.$last = arr => arr[arr.length - 1];
  Vue.prototype.$longestItem = (...vals) => [...vals].sort((a, b) => b.length - a.length)[0];
  Vue.prototype.$mapObject = (arr, fn) =>
    (a => (
      (a = [arr, arr.map(fn)]), a[0].reduce((acc, val, ind) => ((acc[val] = a[1][ind]), acc), {})
    ))();
  Vue.prototype.$maxN = (arr, n = 1) => [...arr].sort((a, b) => b - a).slice(0, n);
  Vue.prototype.$minN = (arr, n = 1) => [...arr].sort((a, b) => a - b).slice(0, n);
  Vue.prototype.$nthElement = (arr, n = 0) => (n > 0 ? arr.slice(n, n + 1) : arr.slice(n))[0];
  Vue.prototype.$partition = (arr, fn) =>
    arr.reduce(
      (acc, val, i, arr) => {
        acc[fn(val, i, arr) ? 0 : 1].push(val);
        return acc;
      },
      [[], []]
    );
  Vue.prototype.$pick = (obj, arr) =>
    arr.reduce((acc, curr) => (curr in obj && (acc[curr] = obj[curr]), acc), {});
  Vue.prototype.$pull = (arr, ...args) => {
    let argState = Array.isArray(args[0]) ? args[0] : args;
    let pulled = arr.filter((v, i) => !argState.includes(v));
    arr.length = 0;
    pulled.forEach(v => arr.push(v));
  };
  Vue.prototype.$pullAtIndex = (arr, pullArr) => {
    let removed = [];
    let pulled = arr
      .map((v, i) => (pullArr.includes(i) ? removed.push(v) : v))
      .filter((v, i) => !pullArr.includes(i));
    arr.length = 0;
    pulled.forEach(v => arr.push(v));
    return removed;
  };
  Vue.prototype.$pullAtValue = (arr, pullArr) => {
    let removed = [],
      pushToRemove = arr.forEach((v, i) => (pullArr.includes(v) ? removed.push(v) : v)),
      mutateTo = arr.filter((v, i) => !pullArr.includes(v));
    arr.length = 0;
    mutateTo.forEach(v => arr.push(v));
    return removed;
  };
  Vue.prototype.$reducedFilter = (data, keys, fn) =>
    data.filter(fn).map(el =>
      keys.reduce((acc, key) => {
        acc[key] = el[key];
        return acc;
      }, {})
    );
  Vue.prototype.$remove = (arr, func) =>
    Array.isArray(arr)
      ? arr.filter(func).reduce((acc, val) => {
        arr.splice(arr.indexOf(val), 1);
        return acc.concat(val);
      }, [])
      : [];
  Vue.prototype.$sample = arr => arr[Math.floor(Math.random() * arr.length)];
  Vue.prototype.$sampleSize = ([...arr], n = 1) => {
    let m = arr.length;
    while (m) {
      const i = Math.floor(Math.random() * m--);
      [arr[m], arr[i]] = [arr[i], arr[m]];
    }
    return arr.slice(0, n);
  };
  Vue.prototype.$shuffle = ([...arr]) => {
    let m = arr.length;
    while (m) {
      const i = Math.floor(Math.random() * m--);
      [arr[m], arr[i]] = [arr[i], arr[m]];
    }
    return arr;
  };
  Vue.prototype.$similarity = (arr, values) => arr.filter(v => values.includes(v));
  Vue.prototype.$sortedIndex = (arr, n) => {
    const isDescending = arr[0] > arr[arr.length - 1];
    const index = arr.findIndex(el => (isDescending ? n >= el : n <= el));
    return index === -1 ? arr.length : index;
  };
  Vue.prototype.$symmetricDifference = (a, b) => {
    const sA = new Set(a),
      sB = new Set(b);
    return [...a.filter(x => !sB.has(x)), ...b.filter(x => !sA.has(x))];
  };
  Vue.prototype.$tail = arr => (arr.length > 1 ? arr.slice(1) : arr);
  Vue.prototype.$take = (arr, n = 1) => arr.slice(0, n);
  Vue.prototype.$takeRight = (arr, n = 1) => arr.slice(arr.length - n, arr.length);
  Vue.prototype.$union = (a, b) => Array.from(new Set([...a, ...b]));
  Vue.prototype.$without = (arr, ...args) => arr.filter(v => !args.includes(v));
  Vue.prototype.$zip = (...arrays) => {
    const maxLength = Math.max(...arrays.map(x => x.length));
    return Array.from({ length: maxLength }).map((_, i) => {
      return Array.from({ length: arrays.length }, (_, k) => arrays[k][i]);
    });
  };
  Vue.prototype.$zipObject = (props, values) =>
    props.reduce((obj, prop, index) => ((obj[prop] = values[index]), obj), {});
  Vue.prototype.$arrayToHtmlList = (arr, listID) =>
    arr.map(item => (document.querySelector('#' + listID).innerHTML += `<li>${item}</li>`));
  Vue.prototype.$bottomVisible = () =>
    document.documentElement.clientHeight + window.scrollY >=
    (document.documentElement.scrollHeight || document.documentElement.clientHeight);
  Vue.prototype.$copyToClipboard = str => {
    const el = document.createElement('textarea');
    el.value = str;
    el.setAttribute('readonly', '');
    el.style.position = 'absolute';
    el.style.left = '-9999px';
    document.body.appendChild(el);
    const selected =
      document.getSelection().rangeCount > 0 ? document.getSelection().getRangeAt(0) : false;
    el.select();
    document.execCommand('copy');
    document.body.removeChild(el);
    if (selected) {
      document.getSelection().removeAllRanges();
      document.getSelection().addRange(selected);
    }
  };
  Vue.prototype.$createElement = str => {
    const el = document.createElement('div');
    el.innerHTML = str;
    return el.firstElementChild;
  };
  Vue.prototype.$createEventHub = () => ({
    hub: Object.create(null),
    emit(event, data) {
      (this.hub[event] || []).forEach(handler => handler(data));
    },
    on(event, handler) {
      if (!this.hub[event]) this.hub[event] = [];
      this.hub[event].push(handler);
    },
    off(event, handler) {
      const i = (this.hub[event] || []).findIndex(h => h === handler);
      if (i > -1) this.hub[event].splice(i, 1);
    }
  });
  Vue.prototype.$currentURL = () => window.location.href;
  Vue.prototype.$detectDeviceType = () =>
    /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)
      ? 'Mobile'
      : 'Desktop';
  Vue.prototype.$elementIsVisibleInViewport = (el, partiallyVisible = false) => {
    const { top, left, bottom, right } = el.getBoundingClientRect();
    const { innerHeight, innerWidth } = window;
    return partiallyVisible
      ? ((top > 0 && top < innerHeight) || (bottom > 0 && bottom < innerHeight)) &&
      ((left > 0 && left < innerWidth) || (right > 0 && right < innerWidth))
      : top >= 0 && left >= 0 && bottom <= innerHeight && right <= innerWidth;
  };
  Vue.prototype.$getScrollPosition = (el = window) => ({
    x: el.pageXOffset !== undefined ? el.pageXOffset : el.scrollLeft,
    y: el.pageYOffset !== undefined ? el.pageYOffset : el.scrollTop
  });
  Vue.prototype.$getStyle = (el, ruleName) => getComputedStyle(el)[ruleName];
  Vue.prototype.$hasClass = (el, className) => el.classList.contains(className);
  Vue.prototype.$hide = (...el) => [...el].forEach(e => (e.style.display = 'none'));
  Vue.prototype.$httpsRedirect = () => {
    if (location.protocol !== 'https:') location.replace('https://' + location.href.split('//')[1]);
  };
  Vue.prototype.$off = (el, evt, fn, opts = false) => el.removeEventListener(evt, fn, opts);
  Vue.prototype.$on = (el, evt, fn, opts = {}) => {
    const delegatorFn = e => e.target.matches(opts.target) && fn.call(e.target, e);
    el.addEventListener(evt, opts.target ? delegatorFn : fn, opts.options || false);
    if (opts.target) return delegatorFn;
  };
  Vue.prototype.$onUserInputChange = callback => {
    let type = 'mouse',
      lastTime = 0;
    const mousemoveHandler = () => {
      const now = performance.now();
      if (now - lastTime < 20)
        (type = 'mouse'), callback(type), document.removeEventListener('mousemove', mousemoveHandler);
      lastTime = now;
    };
    document.addEventListener('touchstart', () => {
      if (type === 'touch') return;
      (type = 'touch'), callback(type), document.addEventListener('mousemove', mousemoveHandler);
    });
  };
  Vue.prototype.$redirect = (url, asLink = true) =>
    asLink ? (window.location.href = url) : window.location.replace(url);
  Vue.prototype.$runAsync = fn => {
    const blob = `var fn = ${fn.toString()}; postMessage(fn());`;
    const worker = new Worker(
      URL.createObjectURL(new Blob([blob]), {
        type: 'application/javascript; charset=utf-8'
      })
    );
    return new Promise((res, rej) => {
      worker.onmessage = ({ data }) => {
        res(data), worker.terminate();
      };
      worker.onerror = err => {
        rej(err), worker.terminate();
      };
    });
  };
  Vue.prototype.$scrollToTop = () => {
    const c = document.documentElement.scrollTop || document.body.scrollTop;
    if (c > 0) {
      window.requestAnimationFrame(scrollToTop);
      window.scrollTo(0, c - c / 8);
    }
  };
  Vue.prototype.$setStyle = (el, ruleName, val) => (el.style[ruleName] = val);
  Vue.prototype.$show = (...el) => [...el].forEach(e => (e.style.display = ''));
  Vue.prototype.$toggleClass = (el, className) => el.classList.toggle(className);
  Vue.prototype.$UUIDGeneratorBrowser = () =>
    ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
      (c ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))).toString(16)
    );
  Vue.prototype.$formatDuration = ms => {
    if (ms < 0) ms = -ms;
    const time = {
      day: Math.floor(ms / 86400000),
      hour: Math.floor(ms / 3600000) % 24,
      minute: Math.floor(ms / 60000) % 60,
      second: Math.floor(ms / 1000) % 60,
      millisecond: Math.floor(ms) % 1000
    };
    return Object.entries(time)
      .filter(val => val[1] !== 0)
      .map(val => val[1] + ' ' + (val[1] !== 1 ? val[0] + 's' : val[0]))
      .join(', ');
  };
  Vue.prototype.$getDaysDiffBetweenDates = (dateInitial, dateFinal) =>
    (dateFinal - dateInitial) / (1000 * 3600 * 24);
  Vue.prototype.$tomorrow = () => new Date(new Date().getTime() + 86400000).toISOString().split('T')[0];

  Vue.prototype.$chainAsync = fns => {
    let curr = 0;
    const next = () => fns[curr++](next);
    next();
  };
  Vue.prototype.$compose = (...fns) => fns.reduce((f, g) => (...args) => f(g(...args)));

  Vue.prototype.$curry = (fn, arity = fn.length, ...args) =>
    arity <= args.length ? fn(...args) : curry.bind(null, fn, arity, ...args);
  Vue.prototype.$defer = (fn, ...args) => setTimeout(fn, 1, ...args);
  Vue.prototype.$functionName = fn => (console.debug(fn.name), fn);
  Vue.prototype.$memoize = fn => {
    const cache = new Map();
    const cached = function (val) {
      return cache.has(val) ? cache.get(val) : cache.set(val, fn.call(this, val)) && cache.get(val);
    };
    cached.cache = cache;
    return cached;
  };
  Vue.prototype.$negate = func => (...args) => !func(...args);
  Vue.prototype.$once = fn => {
    let called = false;
    return function (...args) {
      if (called) return;
      called = true;
      return fn.apply(this, args);
    };
  };
  Vue.prototype.$runPromisesInSeries = ps => ps.reduce((p, next) => p.then(next), Promise.resolve());
  Vue.prototype.$sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
  Vue.prototype.$average = (...nums) => [...nums].reduce((acc, val) => acc + val, 0) / nums.length;
  Vue.prototype.$averageBy = (arr, fn) =>
    arr.map(typeof fn === 'function' ? fn : val => val[fn]).reduce((acc, val) => acc + val, 0) /
    arr.length;
  Vue.prototype.$clampNumber = (num, a, b) => Math.max(Math.min(num, Math.max(a, b)), Math.min(a, b));
  Vue.prototype.$digitize = n => [...`${n}`].map(i => parseInt(i));
  Vue.prototype.$distance = (x0, y0, x1, y1) => Math.hypot(x1 - x0, y1 - y0);
  Vue.prototype.$elo = ([...ratings], kFactor = 32, selfRating) => {
    const [a, b] = ratings;
    const expectedScore = (self, opponent) => 1 / (1 + 10 ** ((opponent - self) / 400));
    const newRating = (rating, i) =>
      (selfRating || rating) + kFactor * (i - expectedScore(i ? a : b, i ? b : a));
    if (ratings.length === 2) {
      return [newRating(a, 1), newRating(b, 0)];
    } else {
      for (let i = 0; i < ratings.length; i++) {
        let j = i;
        while (j < ratings.length - 1) {
          [ratings[i], ratings[j + 1]] = elo([ratings[i], ratings[j + 1]], kFactor);
          j++;
        }
      }
    }
    return ratings;
  };
  Vue.prototype.$factorial = n =>
    n < 0
      ? (() => {
        throw new TypeError('Negative numbers are not allowed!');
      })()
      : n <= 1 ? 1 : n * factorial(n - 1);
  Vue.prototype.$fibonacci = n =>
    Array.from({ length: n }).reduce(
      (acc, val, i) => acc.concat(i > 1 ? acc[i - 1] + acc[i - 2] : i),
      []
    );
  Vue.prototype.$gcd = (...arr) => {
    const _gcd = (x, y) => (!y ? x : gcd(y, x % y));
    return [...arr].reduce((a, b) => _gcd(a, b));
  };
  Vue.prototype.$geometricProgression = (end, start = 1, step = 2) =>
    Array.from({ length: Math.floor(Math.log(end / start) / Math.log(step)) + 1 }).map(
      (v, i) => start * step ** i
    );
  Vue.prototype.$hammingDistance = (num1, num2) => ((num1 ^ num2).toString(2).match(/1/g) || '').length;
  Vue.prototype.$inRange = (n, start, end = null) => {
    if (end && start > end) end = [start, (start = end)][0];
    return end == null ? n >= 0 && n < start : n >= start && n < end;
  };
  Vue.prototype.$isDivisible = (dividend, divisor) => dividend % divisor === 0;
  Vue.prototype.$isEven = num => num % 2 === 0;
  Vue.prototype.$isPrime = num => {
    const boundary = Math.floor(Math.sqrt(num));
    for (var i = 2; i <= boundary; i++) if (num % i == 0) return false;
    return num >= 2;
  };
  Vue.prototype.$lcm = (...arr) => {
    const gcd = (x, y) => (!y ? x : gcd(y, x % y));
    const _lcm = (x, y) => x * y / gcd(x, y);
    return [...arr].reduce((a, b) => _lcm(a, b));
  };
  Vue.prototype.$luhnCheck = num => {
    let arr = (num + '')
      .split('')
      .reverse()
      .map(x => parseInt(x));
    let lastDigit = arr.splice(0, 1)[0];
    let sum = arr.reduce((acc, val, i) => (i % 2 !== 0 ? acc + val : acc + (val * 2) % 9 || 9), 0);
    sum += lastDigit;
    return sum % 10 === 0;
  };
  Vue.prototype.$maxBy = (arr, fn) => Math.max(...arr.map(typeof fn === 'function' ? fn : val => val[fn]));
  Vue.prototype.$median = arr => {
    const mid = Math.floor(arr.length / 2),
      nums = [...arr].sort((a, b) => a - b);
    return arr.length % 2 !== 0 ? nums[mid] : (nums[mid - 1] + nums[mid]) / 2;
  };

  Vue.prototype.$minBy = (arr, fn) => Math.min(...arr.map(typeof fn === 'function' ? fn : val => val[fn]));
  Vue.prototype.$percentile = (arr, val) =>
    100 * arr.reduce((acc, v) => acc + (v < val ? 1 : 0) + (v === val ? 0.5 : 0), 0) / arr.length;
  Vue.prototype.$powerset = arr => arr.reduce((a, v) => a.concat(a.map(r => [v].concat(r))), [[]]);
  Vue.prototype.$primes = num => {
    let arr = Array.from({ length: num - 1 }).map((x, i) => i + 2),
      sqroot = Math.floor(Math.sqrt(num)),
      numsTillSqroot = Array.from({ length: sqroot - 1 }).map((x, i) => i + 2);
    numsTillSqroot.forEach(x => (arr = arr.filter(y => y % x !== 0 || y == x)));
    return arr;
  };
  Vue.prototype.$randomIntegerInRange = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;
  Vue.prototype.$randomNumberInRange = (min, max) => Math.random() * (max - min) + min;
  Vue.prototype.$round = (n, decimals = 0) => Number(`${Math.round(`${n}e${decimals}`)}e-${decimals}`);
  Vue.prototype.$sdbm = str => {
    let arr = str.split('');
    return arr.reduce(
      (hashCode, currentVal) =>
        (hashCode = currentVal.charCodeAt(0) + (hashCode << 6) + (hashCode << 16) - hashCode),
      0
    );
  };
  Vue.prototype.$standardDeviation = (arr, usePopulation = false) => {
    const mean = arr.reduce((acc, val) => acc + val, 0) / arr.length;
    return Math.sqrt(
      arr.reduce((acc, val) => acc.concat((val - mean) ** 2), []).reduce((acc, val) => acc + val, 0) /
      (arr.length - (usePopulation ? 0 : 1))
    );
  };
  Vue.prototype.$sum = (...arr) => [...arr].reduce((acc, val) => acc + val, 0);
  Vue.prototype.$sumBy = (arr, fn) =>
    arr.map(typeof fn === 'function' ? fn : val => val[fn]).reduce((acc, val) => acc + val, 0);
  Vue.prototype.$sumPower = (end, power = 2, start = 1) =>
    Array(end + 1 - start)
      .fill(0)
      .map((x, i) => (i + start) ** power)
      .reduce((a, b) => a + b, 0);
  Vue.prototype.$toSafeInteger = num =>
    Math.round(Math.max(Math.min(num, Number.MAX_SAFE_INTEGER), Number.MIN_SAFE_INTEGER));
  Vue.prototype.$colorize = (...args) => ({
    black: `\x1b[30m${args.join(' ')}`,
    red: `\x1b[31m${args.join(' ')}`,
    green: `\x1b[32m${args.join(' ')}`,
    yellow: `\x1b[33m${args.join(' ')}`,
    blue: `\x1b[34m${args.join(' ')}`,
    magenta: `\x1b[35m${args.join(' ')}`,
    cyan: `\x1b[36m${args.join(' ')}`,
    white: `\x1b[37m${args.join(' ')}`,
    bgBlack: `\x1b[40m${args.join(' ')}\x1b[0m`,
    bgRed: `\x1b[41m${args.join(' ')}\x1b[0m`,
    bgGreen: `\x1b[42m${args.join(' ')}\x1b[0m`,
    bgYellow: `\x1b[43m${args.join(' ')}\x1b[0m`,
    bgBlue: `\x1b[44m${args.join(' ')}\x1b[0m`,
    bgMagenta: `\x1b[45m${args.join(' ')}\x1b[0m`,
    bgCyan: `\x1b[46m${args.join(' ')}\x1b[0m`,
    bgWhite: `\x1b[47m${args.join(' ')}\x1b[0m`
  });
  Vue.prototype.$hasFlags = (...flags) =>
    flags.every(flag => process.argv.includes(/^-{1,2}/.test(flag) ? flag : '--' + flag));
  Vue.prototype.$isTravisCI = () => 'TRAVIS' in process.env && 'CI' in process.env;
  Vue.prototype.$JSONToFile = (obj, filename) =>
    fs.writeFile(`${filename}.json`, JSON.stringify(obj, null, 2));
  Vue.prototype.$readFileLines = filename =>
    fs
      .readFileSync(filename)
      .toString('UTF8')
      .split('\n');
  Vue.prototype.$untildify = str => str.replace(/^~($|\/|\\)/, `${require('os').homedir()}$1`);
  Vue.prototype.$crypto = require('crypto');
  const UUIDGeneratorNode = () =>
    ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
      (c ^ (crypto.randomBytes(1)[0] & (15 >> (c / 4)))).toString(16)
    );
  Vue.prototype.$cleanObj = (obj, keysToKeep = [], childIndicator) => {
    Object.keys(obj).forEach(key => {
      if (key === childIndicator) {
        cleanObj(obj[key], keysToKeep, childIndicator);
      } else if (!keysToKeep.includes(key)) {
        delete obj[key];
      }
    });
    return obj;
  };
  Vue.prototype.$functions = (obj, inherited = false) =>
    (inherited
      ? [...Object.keys(obj), ...Object.keys(Object.getPrototypeOf(obj))]
      : Object.keys(obj)
    ).filter(key => typeof obj[key] === 'function');

  Vue.prototype.$invertKeyValues = obj =>
    Object.keys(obj).reduce((acc, key) => {
      acc[obj[key]] = key;
      return acc;
    }, {});
  Vue.prototype.$lowercaseKeys = obj =>
    Object.keys(obj).reduce((acc, key) => {
      acc[key.toLowerCase()] = obj[key];
      return acc;
    }, {});
  Vue.prototype.$mapKeys = (obj, fn) =>
    Object.keys(obj).reduce((acc, k) => {
      acc[fn(obj[k], k, obj)] = obj[k];
      return acc;
    }, {});
  Vue.prototype.$mapValues = (obj, fn) =>
    Object.keys(obj).reduce((acc, k) => {
      acc[k] = fn(obj[k], k, obj);
      return acc;
    }, {});
  Vue.prototype.$merge = (...objs) =>
    [...objs].reduce(
      (acc, obj) =>
        Object.keys(obj).reduce((a, k) => {
          acc[k] = acc.hasOwnProperty(k) ? [].concat(acc[k]).concat(obj[k]) : obj[k];
          return acc;
        }, {}),
      {}
    );
  Vue.prototype.$objectFromPairs = arr => arr.reduce((a, v) => ((a[v[0]] = v[1]), a), {});
  Vue.prototype.$objectToPairs = obj => Object.keys(obj).map(k => [k, obj[k]]);
  Vue.prototype.$orderBy = (arr, props, orders) =>
    [...arr].sort((a, b) =>
      props.reduce((acc, prop, i) => {
        if (acc === 0) {
          const [p1, p2] = orders && orders[i] === 'desc' ? [b[prop], a[prop]] : [a[prop], b[prop]];
          acc = p1 > p2 ? 1 : p1 < p2 ? -1 : 0;
        }
        return acc;
      }, 0)
    );
  Vue.prototype.$select = (from, ...selectors) =>
    [...selectors].map(s => s.split('.').reduce((prev, cur) => prev && prev[cur], from));
  Vue.prototype.$shallowClone = obj => Object.assign({}, obj);
  Vue.prototype.$size = val =>
    Array.isArray(val)
      ? val.length
      : val && typeof val === 'object'
        ? val.size || val.length || Object.keys(val).length
        : typeof val === 'string' ? new Blob([val]).size : 0;
  Vue.prototype.$transform = (obj, fn, acc) => Object.keys(obj).reduce((a, k) => fn(a, obj[k], k, obj), acc);
  Vue.prototype.$truthCheckCollection = (collection, pre) => collection.every(obj => obj[pre]);
  Vue.prototype.$anagrams = str => {
    if (str.length <= 2) return str.length === 2 ? [str, str[1] + str[0]] : [str];
    return str
      .split('')
      .reduce(
        (acc, letter, i) =>
          acc.concat(anagrams(str.slice(0, i) + str.slice(i + 1)).map(val => letter + val)),
        []
      );
  };
  Vue.prototype.$byteSize = str => new Blob([str]).size;
  Vue.prototype.$capitalize = ([first, ...rest], lowerRest = false) =>
    first.toUpperCase() + (lowerRest ? rest.join('').toLowerCase() : rest.join(''));
  Vue.prototype.$capitalizeEveryWord = str => str.replace(/\b[a-z]/g, char => char.toUpperCase());
  Vue.prototype.$decapitalize = ([first, ...rest], upperRest = false) =>
    first.toLowerCase() + (upperRest ? rest.join('').toUpperCase() : rest.join(''));
  Vue.prototype.$escapeHTML = str =>
    str.replace(
      /[&<>'"]/g,
      tag =>
      ({
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;',
        "'": '&#39;',
        '"': '&quot;'
      }[tag] || tag)
    );
  Vue.prototype.$escapeRegExp = str => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  Vue.prototype.$fromCamelCase = (str, separator = '_') =>
    str
      .replace(/([a-z\d])([A-Z])/g, '$1' + separator + '$2')
      .replace(/([A-Z]+)([A-Z][a-z\d]+)/g, '$1' + separator + '$2')
      .toLowerCase();
  Vue.prototype.$isAbsoluteURL = str => /^[a-z][a-z0-9+.-]*:/.test(str);
  Vue.prototype.$isLowerCase = str => str === str.toLowerCase();
  Vue.prototype.$isUpperCase = str => str === str.toUpperCase();
  Vue.prototype.$mask = (cc, num = 4, mask = '*') =>
    ('' + cc).slice(0, -num).replace(/./g, mask) + ('' + cc).slice(-num);

  Vue.prototype.$palindrome = str => {
    const s = str.toLowerCase().replace(/[\W_]/g, '');
    return (
      s ===
      s
        .split('')
        .reverse()
        .join('')
    );
  };
  Vue.prototype.$pluralize = (val, word, plural = word + 's') => {
    const _pluralize = (num, word, plural = word + 's') =>
      [1, -1].includes(Number(num)) ? word : plural;
    if (typeof val === 'object') return (num, word) => _pluralize(num, word, val[word]);
    return _pluralize(val, word, plural);
  };
  Vue.prototype.$reverseString = str => [...str].reverse().join('');
  Vue.prototype.$sortCharactersInString = str =>
    [...str]
      .sort((a, b) => a.localeCompare(b))
      .join('');
  Vue.prototype.$splitLines = str => str.split(/\r?\n/);
  Vue.prototype.$toCamelCase = str => {
    let s =
      str &&
      str
        .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
        .map(x => x.slice(0, 1).toUpperCase() + x.slice(1).toLowerCase())
        .join('');
    return s.slice(0, 1).toLowerCase() + s.slice(1);
  };
  Vue.prototype.$toKebabCase = str =>
    str &&
    str
      .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
      .map(x => x.toLowerCase())
      .join('-');
  Vue.prototype.$toSnakeCase = str => {
    str &&
      str
        .match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g)
        .map(x => x.toLowerCase())
        .join('_');
  };
  Vue.prototype.$truncateString = (str, num) =>
    str.length > num ? str.slice(0, num > 3 ? num - 3 : num) + '...' : str;
  Vue.prototype.$unescapeHTML = str =>
    str.replace(
      /&amp;|&lt;|&gt;|&#39;|&quot;/g,
      tag =>
      ({
        '&amp;': '&',
        '&lt;': '<',
        '&gt;': '>',
        '&#39;': "'",
        '&quot;': '"'
      }[tag] || tag)
    );
  Vue.prototype.$words = (str, pattern = /[^a-zA-Z-]+/) => str.split(pattern).filter(Boolean);
  Vue.prototype.$getType = v =>
    v === undefined ? 'undefined' : v === null ? 'null' : v.constructor.name.toLowerCase();
  Vue.prototype.$isArray = val => Array.isArray(val);
  Vue.prototype.$isArrayLike = val => {
    try {
      return [...val], true;
    } catch (e) {
      return false;
    }
  };
  Vue.prototype.$isBoolean = val => typeof val === 'boolean';
  Vue.prototype.$isFunction = val => typeof val === 'function';
  Vue.prototype.$isNull = val => val === null;
  Vue.prototype.$isNumber = val => typeof val === 'number';
  Vue.prototype.$isObject = obj => obj === Object(obj);
  Vue.prototype.$isPrimitive = val => !['object', 'function'].includes(typeof val) || val === null;
  Vue.prototype.$isPromiseLike = obj =>
    obj !== null &&
    (typeof obj === 'object' || typeof obj === 'function') &&
    typeof obj.then === 'function';
  Vue.prototype.$isString = val => typeof val === 'string';
  Vue.prototype.$isSymbol = val => typeof val === 'symbol';
  Vue.prototype.$isValidJSON = obj => {
    try {
      JSON.parse(obj);
      return true;
    } catch (e) {
      return false;
    }
  };
  Vue.prototype.$cloneRegExp = regExp => new RegExp(regExp.source, regExp.flags);
  Vue.prototype.$coalesce = (...args) => args.find(_ => ![undefined, null].includes(_));
  Vue.prototype.$coalesceFactory = valid => (...args) => args.find(valid);
  Vue.prototype.$extendHex = shortHex =>
    '#' +
    shortHex
      .slice(shortHex.startsWith('#') ? 1 : 0)
      .split('')
      .map(x => x + x)
      .join('');

  Vue.prototype.$getURLParameters = url =>
    url
      .match(/([^?=&]+)(=([^&]*))/g)
      .reduce((a, v) => ((a[v.slice(0, v.indexOf('='))] = v.slice(v.indexOf('=') + 1)), a), {});
  Vue.prototype.$hexToRGB = hex => {
    let alpha = false,
      h = hex.slice(hex.startsWith('#') ? 1 : 0);
    if (h.length === 3) h = [...h].map(x => x + x).join('');
    else if (h.length === 8) alpha = true;
    h = parseInt(h, 16);
    return (
      'rgb' +
      (alpha ? 'a' : '') +
      '(' +
      (h >>> (alpha ? 24 : 16)) +
      ', ' +
      ((h & (alpha ? 0x00ff0000 : 0x00ff00)) >>> (alpha ? 16 : 8)) +
      ', ' +
      ((h & (alpha ? 0x0000ff00 : 0x0000ff)) >>> (alpha ? 8 : 0)) +
      (alpha ? `, ${h & 0x000000ff}` : '') +
      ')'
    );
  };
  Vue.prototype.$httpGet = (url, callback, err = console.error) => {
    const request = new XMLHttpRequest();
    request.open('GET', url, true);
    request.onload = () => callback(request.responseText);
    request.onerror = () => err(request);
    request.send();
  };
  Vue.prototype.$httpPost = (url, callback, data = null, err = console.error) => {
    const request = new XMLHttpRequest();
    request.open('POST', url, true);
    request.setRequestHeader('Content-type', 'application/json; charset=utf-8');
    request.onload = () => callback(request.responseText);
    request.onerror = () => err(request);
    request.send(data);
  };
  Vue.prototype.$parseCookie = str =>
    str
      .split(';')
      .map(v => v.split('='))
      .reduce((acc, v) => {
        acc[decodeURIComponent(v[0].trim())] = decodeURIComponent(v[1].trim());
        return acc;
      }, {});
  Vue.prototype.$prettyBytes = (num, precision = 3, addSpace = true) => {
    const UNITS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
    if (Math.abs(num) < 1) return num + (addSpace ? ' ' : '') + UNITS[0];
    const exponent = Math.min(Math.floor(Math.log10(num < 0 ? -num : num) / 3), UNITS.length - 1);
    const n = Number(((num < 0 ? -num : num) / 1000 ** exponent).toPrecision(precision));
    return (num < 0 ? '-' : '') + n + (addSpace ? ' ' : '') + UNITS[exponent];
  };
  Vue.prototype.$randomHexColorCode = () => {
    let n = ((Math.random() * 0xfffff) | 0).toString(16);
    return '#' + (n.length !== 6 ? ((Math.random() * 0xf) | 0).toString(16) + n : n);
  };
  Vue.prototype.$RGBToHex = (r, g, b) => ((r << 16) + (g << 8) + b).toString(16).padStart(6, '0');
  Vue.prototype.$serializeCookie = (name, val) => `${encodeURIComponent(name)}=${encodeURIComponent(val)}`;
  Vue.prototype.$timeTaken = callback => {
    console.time('timeTaken');
    const r = callback();
    console.timeEnd('timeTaken');
    return r;
  };
  Vue.prototype.$toDecimalMark = num => num.toLocaleString('en-US');
  Vue.prototype.$toOrdinalSuffix = num => {
    const int = parseInt(num),
      digits = [int % 10, int % 100],
      ordinals = ['st', 'nd', 'rd', 'th'],
      oPattern = [1, 2, 3, 4],
      tPattern = [11, 12, 13, 14, 15, 16, 17, 18, 19];
    return oPattern.includes(digits[0]) && !tPattern.includes(digits[1])
      ? int + ordinals[digits[0] - 1]
      : int + ordinals[3];
  };
  Vue.prototype.$validateNumber = n => !isNaN(parseFloat(n)) && isFinite(n) && Number(n) == n;
  Vue.prototype.$yesNo = (val, def = false) =>
    /^(y|yes)$/i.test(val) ? true : /^(n|no)$/i.test(val) ? false : def;

};

export default install